﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IO;
using System.Collections;

namespace Portal.FileSystemData
{
  /// <summary>
  /// Summary description for DirectoryData
  /// </summary>
  public class DirectoryWrapper : FileSystemWrapper
  {
    /// <summary>
    /// File which contains the data for the directory.
    /// </summary>
    public const string CustomDataFile = "_directory.config";

    /// <summary>
    /// The sort criteria.
    /// </summary>
    public enum SortProperty : int
    {
      Name = 0,
      Description = 1,
      ModDate = 2
    }

    protected List<DirectoryWrapper> _subDirectories;
    protected List<FileWrapper> _files;
    private DirectoryData _xmlDirData;
    private DirectoryData.DirectoryConfigRow _customData;

    /// <summary>
    /// Constructor.
    /// </summary>
    /// <param name="path">The physical path to the directory</param>
    /// <param name="parent">The parent directory</param>
    public DirectoryWrapper(string path, DirectoryWrapper parent)
      : base(new DirectoryInfo(path), parent)
    {
      
    }


    /// <summary>
    /// Renames the item.
    /// </summary>
    /// <param name="newName">The new name</param>
    protected override void RenameTo(string newName)
    {
      if (_parent == null)
        throw new InvalidOperationException("Rename of the root directory is not allowed");
      string newPath = Path.Combine(_parent.PhysicalPath, newName);
      ((DirectoryInfo)_fileSystemInfo).MoveTo(newPath);
      
      // Reset the cached data. They will be refreshed by the next access.
      _virtualPath = null;
      _physicalPathRelative = null;
      _subDirectories = null;
      _files = null;
    }

    /// <summary>
    /// The relative virtual path to this item.
    /// </summary>
    public override string VirtualPath
    {
      get
      {
        if (_virtualPath == null)
        {
          _virtualPath = VirtualPathUtility.AppendTrailingSlash(base.VirtualPath);
        }
        return _virtualPath;
      }
    }


    /// <summary>
    /// The contained subdirectories.
    /// </summary>
    public DirectoryWrapper[] SubDirectories
    {
      get
      {
        // Read the directories only the first time.
        if(null == _subDirectories)
        {
          UpdateSubDirectories();
        }
        return _subDirectories.ToArray();
      }
    }

    /// <summary>
    /// Retrieves the contained subdirectories in a paged form.
    /// </summary>
    /// <param name="startRowIndex">The index to start with</param>
    /// <param name="maximumRows">The maximum number of items in the result. -1 for infinite.</param>
    /// <returns></returns>
    public DirectoryWrapper[] GetSubDirectories(int startRowIndex, int maximumRows)
    {
      if (null == _subDirectories)
        UpdateSubDirectories();

      if (maximumRows < 0)
        maximumRows = _subDirectories.Count - startRowIndex;

      return _subDirectories.Skip(startRowIndex).Take(maximumRows).ToArray();
    }

    /// <summary>
    /// Update the cached subdirectories.
    /// </summary>
    protected void UpdateSubDirectories()
    {
      DirectoryInfo[] subDir = ((DirectoryInfo)_fileSystemInfo).GetDirectories();
      int nofDir = subDir.Length;
      _subDirectories = new List<DirectoryWrapper>(nofDir);
      foreach (DirectoryInfo dirInfo in subDir)
      {
        if (dirInfo.Name[0] != '_')
        {
          // Create the Wrapper with the same sort order.
          DirectoryWrapper dirWrapper = CreateDirectoryData(dirInfo.FullName);
          dirWrapper.Sort(false);
          _subDirectories.Add(dirWrapper);
        }
      }
    }


    /// <summary>
    /// Creates a new Directory item. This method can be overriden in subclasses.
    /// </summary>
    /// <param name="path">The physical path to the directory</param>
    /// <returns></returns>
    protected virtual DirectoryWrapper CreateDirectoryData(string path)
    {
      return new DirectoryWrapper(path, this);
    }


    /// <summary>
    /// The contained files.
    /// </summary>
    public FileWrapper[] Files
    {
      get
      {
        // Read the files only the first time.
        if (null == _files)
        {
          UpdateFiles();
        }

        return _files.ToArray();
      }
    }


    /// <summary>
    /// Retrieves the contained files in a paged form.
    /// </summary>
    /// <param name="startRowIndex">The index to start with</param>
    /// <param name="maximumRows">The maximum number of items in the result. -1 for infinite.</param>
    /// <returns></returns>
    public FileWrapper[] GetFiles(int startRowIndex, int maximumRows)
    {
      if (null == _files)
        UpdateFiles();

      if (maximumRows < 0)
        maximumRows = _files.Count - startRowIndex;

      return _files.Skip(startRowIndex).Take(maximumRows).ToArray();
    }

    /// <summary>
    /// Update the cached files.
    /// </summary>
    private void UpdateFiles()
    {
      FileInfo[] contFiles = ((DirectoryInfo)_fileSystemInfo).GetFiles();
      int nofFiles = contFiles.Length;
      _files = new List<FileWrapper>(nofFiles);
      foreach (FileInfo fileData in contFiles)
      {
        if (fileData.Name[0] != '_')
        {
          _files.Add(CreateFileData(fileData.FullName));
        }
      }
      // Sortieren der Liste mit den Dateien.
      SortFiles();
    }

    /// <summary>
    /// Creates a new File item. This method can be overriden in subclasses.
    /// </summary>
    /// <param name="path">The physical path to the file</param>
    /// <returns></returns>
    protected virtual FileWrapper CreateFileData(string path)
    {
      // Load the additional data for this file.
      string name = Path.GetFileName(path);
      DirectoryData.FileConfigRow fileDataRow = XmlDirectoryData.FileConfig.FindByName(name);
      if (null == fileDataRow)
      {
        fileDataRow = XmlDirectoryData.FileConfig.NewFileConfigRow();
        fileDataRow.Name = name;
        fileDataRow.ModificationDate = DateTime.Now;
        XmlDirectoryData.FileConfig.AddFileConfigRow(fileDataRow);
      }
      return new FileWrapper(path, this, fileDataRow);
    }


    /// <summary>
    /// All directory content (Subdirectories and files).
    /// </summary>
    public FileSystemWrapper[] AllItems
    {
      get
      {
        if (_subDirectories == null)
          UpdateSubDirectories();

        if (_files == null)
          UpdateFiles();

        return ((FileSystemWrapper[])SubDirectories).Union((FileSystemWrapper[])Files).ToArray();
      }
    }

    /// <summary>
    /// Retrieves the items in a paged form.
    /// </summary>
    /// <param name="startRowIndex">The index to start with</param>
    /// <param name="maximumRows">The maximum number of items in the result. -1 for infinite.</param>
    /// <returns></returns>
    public FileSystemWrapper[] GetItems(int startRowIndex, int maximumRows, bool includeDirectories)
    {
      if(startRowIndex < 0)
        throw new ArgumentException("startRowIndex");

      if(startRowIndex == 0 && maximumRows == 0)
        return AllItems;

      int dirStartIndex = 0;
      int nofDirToIncl = 0;
      int fileStartIndex = 0;
      int nofFilesToIncl = 0;

      if (includeDirectories)
      {
        if (_subDirectories == null)
          UpdateSubDirectories();

        // Calculate the range for the directories.
        if (_subDirectories.Count > startRowIndex)
        {
          dirStartIndex = startRowIndex;
          nofDirToIncl = _subDirectories.Count - startRowIndex;
          if ((maximumRows >= 0) && (nofDirToIncl > maximumRows))
            nofDirToIncl = maximumRows;
        }
        else
          fileStartIndex = startRowIndex - _subDirectories.Count;
      }

      // Calculate the range for the files.
      if((maximumRows < 0) || (nofDirToIncl < maximumRows))
      {
        if (_files == null)
          UpdateFiles();

        nofFilesToIncl = _files.Count - fileStartIndex;
        if ((maximumRows >= 0) && (nofFilesToIncl > (maximumRows - nofDirToIncl)))
          nofFilesToIncl = (maximumRows - nofDirToIncl);
      }
      
      // Shortcuts if only one category is used.
      if (nofFilesToIncl == 0)
      {
        if(nofDirToIncl == _subDirectories.Count)
          return SubDirectories;
        else
          return _subDirectories.Skip(dirStartIndex).Take(nofDirToIncl).ToArray();
      }
        
      if (nofDirToIncl == 0)
      {
        if(nofFilesToIncl == _files.Count)
          return Files;
        else
          return _files.Skip(fileStartIndex).Take(nofFilesToIncl).ToArray();
      }

      var dirs = ((FileSystemWrapper[])SubDirectories).Skip(dirStartIndex).Take(nofDirToIncl);
      var files = ((FileSystemWrapper[])Files).Skip(fileStartIndex).Take(nofFilesToIncl);
      return dirs.Union(files).ToArray();
    }

    /// <summary>
    /// The number of files contained in this directory.
    /// </summary>
    public int FileCount
    {
      get
      {
        if (_files == null)
          UpdateFiles();

        return _files.Count;
      }
    }


    /// <summary>
    /// The number of subdirectories contained in this directory.
    /// </summary>
    public int SubDirCount
    {
      get
      {
        if (_subDirectories == null)
          UpdateSubDirectories();
        return _subDirectories.Count;
      }
    }

    /// <summary>
    /// Retrieves the directory data for the given path.
    /// </summary>
    /// <param name="dirPath">The relative virtual path of the directory</param>
    /// <returns>The directorydata or null if not found</returns>
    public DirectoryWrapper GetSubDirectory(string dirPath)
    {
      // jump over the first slash.
      int slashPos = dirPath.IndexOf('/');
      if (slashPos == 0)
      {
        dirPath = dirPath.Substring(1);
        slashPos = dirPath.IndexOf('/');
      }

      // expand the directory data.
      string subDirName;
      if (slashPos != -1)
      {
        subDirName = dirPath.Substring(0, slashPos);
        dirPath = dirPath.Substring(slashPos + 1);
        if (dirPath == "/")
          dirPath = String.Empty;
      }
      else
        subDirName = dirPath;

      // Search the directory
      DirectoryWrapper subDir = Array.Find(SubDirectories, dir => { return dir.Name == subDirName; });
      if (subDir != null)
      {
        if (String.IsNullOrEmpty(dirPath))
          return subDir;
        else
          return subDir.GetSubDirectory(dirPath);
      }
      else
        return null;  // Not found.
    }


    /// <summary>
    /// Retrieves the file with the given path.
    /// </summary>
    /// <param name="filePath">The relative virtual path to the file</param>
    /// <returns>The file data, or null if not found</returns>
    public FileWrapper GetFile(string filePath)
    {
      // jump over the first slash.
      int slashPos = filePath.IndexOf('/');
      if (slashPos == 0)
      {
        filePath = filePath.Substring(1);
        slashPos = filePath.IndexOf('/');
      }
      if ((-1 == slashPos))
      {
        // If there is no subdirectory, then search for the file..
        return Array.Find(Files, file => { return file.Name == filePath; });
      }
      else
      {
        // search the directory.
        string dirName = filePath.Substring(0, slashPos);
        DirectoryWrapper subDir = Array.Find(SubDirectories, dir => { return dir.Name == dirName; });
        if (subDir != null)
          return subDir.GetFile(filePath.Substring(slashPos + 1));
        else
          return null;  // Not found.
      }
    }

    /// <summary>
    /// Retrieves the file with the given path and the siblings as outparameters
    /// </summary>
    /// <param name="filePath">The relative virtual path to the file</param>
    /// <param name="filePath">The relative virtual path to the file</param>
    /// <returns>The file data, or null if not found</returns>
    public FileWrapper GetFileWithSiblings(string filePath, out FileWrapper previous, out FileWrapper next)
    {
      // jump over the first slash.
      int slashPos = filePath.IndexOf('/');
      if (slashPos == 0)
      {
        filePath = filePath.Substring(1);
        slashPos = filePath.IndexOf('/');
      }
      if ((-1 == slashPos))
      {
        // If there is no subdirectory, then search for the file.
        int index = Array.FindIndex(Files, file => { return file.Name == filePath; });
        if (index >= 0)
        {
          previous = (index > 0) ? Files[index - 1] : null;
          next = (index < Files.Length - 1) ? Files[index + 1] : null;
          return Files[index];
        }
        else
        {
          previous = null;
          next = null;
          return null;  // Not found.
        }
      }
      else
      {
        // search the directory.
        string dirName = filePath.Substring(0, slashPos);
        DirectoryWrapper subDir = Array.Find(SubDirectories, dir => { return dir.Name == dirName; });
        if (subDir != null)
          return subDir.GetFileWithSiblings(filePath.Substring(slashPos + 1), out previous, out next);
        else
        {
          previous = null;
          next = null;
          return null;  // Not found.
        }
      }
    }

    /// <summary>
    /// Retrieves the index of the file in this folder (inclusive directories).
    /// </summary>
    /// <param name="fileName">The filename to search for</param>
    /// <returns>the index of the file. -1 if the file doesnt exist.</returns>
    public int GetFileIndex(string fileName)
    {
      int index = Array.FindIndex(Files, file => { return 0 == string.Compare(file.Name, fileName, true); });
      if (index >= 0)
        index += SubDirCount;
      return index;
    }


    /// <summary>
    /// Add a new file
    /// </summary>
    /// <param name="fileName">filename</param>
    public FileWrapper AddFile(string fileName)
    {
      FileWrapper fw = CreateFileData(Path.Combine(PhysicalPath, fileName));
      _files.Add(fw);
      SortFiles();
      return fw;
    }

    /// <summary>
    /// Creates a new subdirectory.
    /// </summary>
    /// <param name="dirName"></param>
    public void CreateSubDir(string dirName)
    {
      ((DirectoryInfo)_fileSystemInfo).CreateSubdirectory(dirName);

      // Reload the cached data.
      _subDirectories = null;
    }


    /// <summary>
    /// Sortiert die Dateien mit den aktuellen Sortiereinstellungen.
    /// </summary>
    protected void SortFiles()
    {
      if (_files != null)
      {
        int negFactor = 1;
        if (!SortOrderAsc)
          negFactor = -1;

        switch (SortCriteria)
        {
          case SortProperty.Name:
            {
              _files.Sort((file1, file2) => { return negFactor * file1.Name.CompareTo(file2.Name); });
              break;
            }
          case SortProperty.ModDate:
            {
              _files.Sort((file1, file2) => { return negFactor * file1.ModificationDate.CompareTo(file2.ModificationDate); });
              break;
            }
          case SortProperty.Description:
            {
              _files.Sort((file1, file2) => { return negFactor * file1.Description.CompareTo(file2.Description); });
              break;
            }
          default:
            throw new ArgumentOutOfRangeException();
        }
      }
    }


    /// <summary>
    /// Applies the sort order.
    /// </summary>
    /// <param name="recursive">Sort the subdirectories too?</param>
    public void Sort(bool recursive)
    {
      SortFiles();

      if (recursive)
      {
        if (_subDirectories != null)
        {
          foreach (DirectoryWrapper dir in _subDirectories)
            dir.Sort(recursive);
        }
      }
    }

    /// <summary>
    /// Deletes the directory.
    /// </summary>
    public void Delete()
    {
      if (Parent == null)
        throw new InvalidOperationException("Deleting of the root item is not allowed");

      ((DirectoryInfo)_fileSystemInfo).Delete(true);
      _parent.Detach(this);
    }


    /// <summary>
    /// Removes a file from the list without deleting the physical file.
    /// </summary>
    /// <param name="file">The file to detach.</param>
    public void Detach(FileWrapper file)
    {
      if (_files != null)
      {
        _files.Remove(file);
      }
    }


    /// <summary>
    /// Removes a directory from the list without deleting the physical directory.
    /// </summary>
    /// <param name="file">The directory to detach.</param>
    protected void Detach(DirectoryWrapper dir)
    {
      if (_subDirectories != null)
      {
        _subDirectories.Remove(dir);
      }
    }

    /// <summary>
    /// The name for displaying in user interfaces.
    /// </summary>
    public override string PresentationName
    {
      get { return CustomData.PresentationName; }
      set
      {
        if (CustomData.PresentationName != value)
        {
          CustomData.PresentationName = value;

          // If it's not the root directory, then apply the change to the physical name too.
          if (Parent != null)
            Name = value;

          SaveData();
        }
      }
    }


    /// <summary>
    /// The string to use as tooltip for the directory.
    /// </summary>
    public override string Tooltip
    {
      get { return CustomData.Tooltip; }
      set 
      {
        if (CustomData.Tooltip != value)
        {
          CustomData.Tooltip = value;
          SaveData();
        }
      }
    }


    /// <summary>
    /// The string to use as description for the item.
    /// </summary>
    public override string Description
    {
      get { return CustomData.Description; }
      set 
      {
        if (CustomData.Description != value)
        {
          CustomData.Description = value;
          SaveData();
        }
      }
    }

    public SortProperty SortCriteria
    {
      get { return (SortProperty) Enum.ToObject(typeof(SortProperty), CustomData.ContentOrderCriteria); }
      set
      {
        if (CustomData.ContentOrderCriteria != Convert.ToInt32(value))
        {
          CustomData.ContentOrderCriteria = Convert.ToInt32(value);
          SaveData();
        }
      }
    }

    /// <summary>
    /// Sort Order of the directory content.
    /// </summary>
    public bool SortOrderAsc
    {
      get { return CustomData.ContentOrderAscending; }
      set
      {
        if (CustomData.ContentOrderAscending != value)
        {
          CustomData.ContentOrderAscending = value;
          SaveData();
        }
      }
    }

    /// <summary>
    /// Retrieves the configured xml data.
    /// </summary>
    protected DirectoryData XmlDirectoryData
    {
      get
      {
        if (null == _xmlDirData)
        {
          _xmlDirData = new DirectoryData();
          string cnfgPath = Path.Combine(PhysicalPath, CustomDataFile);
          if (System.IO.File.Exists(cnfgPath))
          {
            _xmlDirData.ReadXml(cnfgPath);
          }
        }
        return _xmlDirData;
      }
    }

    /// <summary>
    /// Retrieves the configured xml data for this directory.
    /// </summary>
    protected DirectoryData.DirectoryConfigRow CustomData
    {
      get
      {
        if (null == _customData)
        {
          if (XmlDirectoryData.DirectoryConfig.Rows.Count < 1)
          {
            XmlDirectoryData.DirectoryConfig.AddDirectoryConfigRow(Name, "", "", true, 0);
          }
          _customData = XmlDirectoryData.DirectoryConfig[0];
        }
        return _customData;
      }
    }


    /// <summary>
    /// Saves the customs data in the xml file.
    /// </summary>
    public void SaveData()
    {
      if (!_isBulkUpdating)
      {
        if (_xmlDirData != null)
        {
          string cnfgPath = Path.Combine(PhysicalPath, CustomDataFile);
          _xmlDirData.WriteXml(cnfgPath);
        }
      }
    }

    /// <summary>
    /// Will be set to true during a bulk update to prevent multiple save operations of the configuration file.
    /// </summary>
    bool _isBulkUpdating = false;

    /// <summary>
    /// Begins with the bulk update to prevent multiple save operations of the configuration file.
    /// </summary>
    public void BeginBulkUpdate()
    {
      _isBulkUpdating = true;
    }

    /// <summary>
    /// Ends the bulk update and saves the changes.
    /// </summary>
    public void EndBulkUpdate()
    {
      if (_isBulkUpdating)
      {
        _isBulkUpdating = false;
        SaveData();
      }
    }

    /// <summary>
    /// Is this item a directory?
    /// </summary>
    public override bool IsDirectory
    {
      get { return true; }
    }
  }
}